﻿using System;
using System.Collections.Generic;
using JTLeigh.Commerce.Spencer.Core.Business;
using JTLeigh.Commerce.Spencer.Core.Common;
using JTLeigh.Commerce.Spencer.Core.Common.Entities;
using JTLeigh.Commerce.Spencer.Core.Common.Services;
using JTLeigh.Commerce.Spencer.Core.Service.Contracts;
using JTLeigh.Commerce.Spencer.Features.ProductCatalog.Service.Contracts;
using JTLeigh.Commerce.Spencer.Features.ProductCatalog.Service.Contracts.GetAllCategoryPathsToProduct;
using JTLeigh.Commerce.Spencer.Features.ProductFeedback.Business.Caches;
using JTLeigh.Commerce.Spencer.Features.ProductFeedback.Common;
using JTLeigh.Commerce.Spencer.Features.ProductFeedback.Common.Entities;
using JTLeigh.Commerce.Spencer.Features.ProductFeedback.Data.Providers;
using Microsoft.Practices.Unity;
using ProductFeedbackE = JTLeigh.Commerce.Spencer.Features.ProductFeedback.Common.Entities.ProductFeedback;

namespace JTLeigh.Commerce.Spencer.Features.ProductFeedback.Business.Services
{

	/// <summary>
	/// An <see cref="IProductFeedbackMetadataService"/> implementation that retrieves product 
	/// feedback metadata.
	/// </summary>
	public class ProductFeedbackMetadataService : IProductFeedbackMetadataService
	{

		#region Properties

		/// <summary>
		/// Gets or sets the product catalog service to use to access the product catalog.
		/// </summary>
		/// <remarks>
		/// <para>
		/// If the service is to be hosted in-process and accessed directly, this will be the
		/// service implementation.  If the service is to be hosted in- or out-of-process and
		/// accessed via WCF, this will be the service client.
		/// </para>
		/// <para>
		/// We need to use the product catalog service to retrieve all of the paths to the product 
		/// through the hierarchy in order to determine which attributes apply to the product.
		/// </para>
		/// </remarks>
		[Dependency]
		public IProductCatalogService ProductCatalogService { get; set; }

		/// <summary>
		/// Gets or sets the product feedback metadata provider to use to get product feedback 
		/// metadata from the database.
		/// </summary>
		/// <value>An <see cref="IProductFeedbackMetadataProvider"/> implementation.</value>
		[Dependency]
		public IProductFeedbackMetadataProvider ProductFeedbackMetadataProvider { get; set; }

		/// <summary>
		/// Gets or sets the product feedback metadata cache to use to cache product feedback 
		/// metadata.
		/// </summary>
		[Dependency]
		public IProductFeedbackMetadataCache ProductFeedbackMetadataCache { get; set; }

		/// <summary>
		/// Gets or sets the exception handling service to use to handle exceptions.
		/// </summary>
		/// <value>An <see cref="IExceptionHandlingService"/> implementation.</value>
		[Dependency]
		public IExceptionHandlingService ExceptionHandlingService { get; set; }

		#endregion // Properties

		#region IProductFeedbackMetadataService Members

        /// <summary>
        /// Gets the product feedback badges that are relevant to the specified product and
        /// feedback, localized for the specified culture, as of the specified effective date.
        /// </summary>
        /// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
        /// badges are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// containing the product whose badges are to be retrieved.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product 
        /// whose badges are to be retrieved.</param>
        /// <param name="effectiveDate">The date to use as the current date when deciding whether 
        /// to include an badge association or disassociation.</param>
        /// <param name="feedback">The <see cref="IList{T}"/> of the <see cref="ProductFeedbackE"/>
        /// whose badges are to be retrieved.</param>
        /// <returns>A <see cref="Dictionary{TKey,TValue}"/> of <see cref="ProductFeedbackBadge"/>
        /// keyed by badge identifier <see cref="Guid"/>.</returns>
        public Dictionary<Guid, ProductFeedbackBadge> GetProductFeedbackBadges(Guid tenantId, string catalogId, string productId, DateTime effectiveDate, IList<ProductFeedbackE> feedback)
        {
            Dictionary<Guid, AssociationBase> badgeAssociations;
            Dictionary<Guid, ProductFeedbackBadge> badges;
            AssociationBase badgeAssociation;
            ProductFeedbackBadge badge;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);
            Safety.AssertArgumentNotNull("feedback", feedback);

            try
            {
                badgeAssociations = GetProductFeedbackBadgeAssociations(tenantId, catalogId, productId, effectiveDate);
                badges = new Dictionary<Guid, ProductFeedbackBadge>();
                foreach (ProductFeedbackE feedbackE in feedback)
                {
                    foreach (ProductFeedbackBadgeAssociation badgeValue in feedbackE.BadgeAssociations.Values)
                    {
                        if (badgeAssociations.TryGetValue(badgeValue.BadgeId, out badgeAssociation))
                        {
                            if (!badges.ContainsKey(badgeValue.BadgeId))
                            {
                                badge = GetProductFeedbackBadge(tenantId, badgeValue.BadgeId);
                                if (badge != null)
                                {
                                    badges.Add(badgeValue.BadgeId, badge);
                                }
                            }
                        }
                    }
                }
                return badges;
            }
            catch (Exception e)
            {
                ExceptionHandlingService.HandleException(tenantId, e, ProductFeedbackExceptionPolicy.Business);
                throw;
            }
        }

        /// <summary>
		/// Gets the product rating attributes that are relevant to the specified product and
		/// feedback, localized for the specified culture, as of the specified effective date.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// attributes are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// containing the product whose attributes are to be retrieved.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product 
		/// whose attributes are to be retrieved.</param>
		/// <param name="cultureId">The <see cref="Int32"/> LCID of the culture to use to retrieve
		/// culture-specific resources.</param>
		/// <param name="effectiveDate">The date to use as the current date when deciding whether 
		/// to include an attribute association or disassociation.</param>
		/// <param name="feedback">The <see cref="IList{T}"/> of the <see cref="ProductFeedbackE"/>
		/// whose attributes are to be retrieved.</param>
		/// <returns>A <see cref="Dictionary{TKey,TValue}"/> of <see cref="LocalizedProductRatingAttribute"/>
		/// keyed by attribute identifier <see cref="Guid"/>.</returns>
		public Dictionary<Guid, LocalizedProductRatingAttribute> GetProductRatingAttributes(Guid tenantId, string catalogId, string productId, int cultureId, DateTime effectiveDate, IList<ProductFeedbackE> feedback)
		{
			Dictionary<Guid, ProductRatingAttributeAssociationBase> attributeAssociations;
			Dictionary<Guid, LocalizedProductRatingAttribute> attributes;
			ProductRatingAttributeAssociationBase attributeAssociation;
			LocalizedProductRatingAttribute attribute;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);
			Safety.AssertArgumentNotNull("feedback", feedback);

			try
			{
				attributeAssociations = GetProductRatingAttributeAssociations(tenantId, catalogId, productId, effectiveDate);
				attributes = new Dictionary<Guid, LocalizedProductRatingAttribute>();
				foreach (ProductFeedbackE feedbackE in feedback)
				{
                    if (feedbackE.Rating != null)
                    {
                        foreach (ProductRatingAttributeValue attributeValue in feedbackE.Rating.AttributeValues.Values)
                        {
                            if (attributeAssociations.TryGetValue(attributeValue.AttributeId, out attributeAssociation))
                            {
                                if (!attributes.ContainsKey(attributeValue.AttributeId))
                                {
                                    attribute = GetProductRatingAttribute(tenantId, attributeValue.AttributeId, cultureId, attributeAssociation);
                                    if (attribute != null)
                                    {
                                        attributes.Add(attributeValue.AttributeId, attribute);
                                    }
                                }
                            }
                        }
                    }
				}
				return attributes;
			}
			catch (Exception e)
			{
				ExceptionHandlingService.HandleException(tenantId, e, ProductFeedbackExceptionPolicy.Business);
				throw;
			}
		}

		/// <summary>
		/// Gets the product recommendation attributes that are relevant to the specified product 
		/// and feedback, localized for the specified culture, as of the specified effective date.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// attributes are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// containing the product whose attributes are to be retrieved.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product 
		/// whose attributes are to be retrieved.</param>
		/// <param name="cultureId">The <see cref="Int32"/> LCID of the culture to use to retrieve
		/// culture-specific resources.</param>
		/// <param name="effectiveDate">The date to use as the current date when deciding whether 
		/// to include an attribute association or disassociation.</param>
		/// <param name="feedback">The <see cref="IList{T}"/> of the <see cref="ProductFeedbackE"/>
		/// whose attributes are to be retrieved.</param>
		/// <returns>A <see cref="Dictionary{TKey,TValue}"/> of <see cref="LocalizedProductRecommendationAttribute"/>
		/// keyed by attribute identifier <see cref="Guid"/>.</returns>
		public Dictionary<Guid, LocalizedProductRecommendationAttribute> GetProductRecommendationAttributes(Guid tenantId, string catalogId, string productId, int cultureId, DateTime effectiveDate, IList<ProductFeedbackE> feedback)
		{
			Dictionary<Guid, ProductRecommendationAttributeAssociationBase> attributeAssociations;
			ProductRecommendationAttributeAssociationBase attributeAssociation;
			Dictionary<Guid, LocalizedProductRecommendationAttribute> attributes;
			LocalizedProductRecommendationAttribute attribute;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);
			Safety.AssertArgumentNotNull("feedback", feedback);

			try
			{
				attributeAssociations = GetProductRecommendationAttributeAssociations(tenantId, catalogId, productId, effectiveDate);
				attributes = new Dictionary<Guid, LocalizedProductRecommendationAttribute>();
				foreach (ProductFeedbackE feedbackE in feedback)
				{
                    if (feedbackE.Recommendation != null)
                    {
                        foreach (ProductRecommendationAttributeValue attributeValue in feedbackE.Recommendation.AttributeValues.Values)
                        {
                            if (attributeAssociations.TryGetValue(attributeValue.AttributeId, out attributeAssociation))
                            {
                                if (!attributes.ContainsKey(attributeValue.AttributeId))
                                {
                                    attribute = GetProductRecommendationAttribute(tenantId, attributeValue.AttributeId, cultureId, attributeAssociation);
                                    if (attribute != null)
                                    {
                                        attributes.Add(attributeValue.AttributeId, attribute);
                                    }
                                }
                            }
                        }
                    }
				}
				return attributes;
			}
			catch (Exception e)
			{
				ExceptionHandlingService.HandleException(tenantId, e, ProductFeedbackExceptionPolicy.Business);
				throw;
			}
		}

		/// <summary>
		/// Gets the product review attributes that are relevant to the specified product and
		/// feedback, localized for the specified culture, as of the specified effective date.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// attributes are to be retrieved.</param>
        /// <param name="catalogId">The catalog identifier <see cref="String"/> of the catalog
        /// containing the product whose attributes are to be retrieved.</param>
        /// <param name="productId">The product identifier <see cref="String"/> of the product 
		/// whose attributes are to be retrieved.</param>
		/// <param name="cultureId">The <see cref="Int32"/> LCID of the culture to use to retrieve
		/// culture-specific resources.</param>
		/// <param name="effectiveDate">The date to use as the current date when deciding whether 
		/// to include an attribute association or disassociation.</param>
		/// <param name="feedback">The <see cref="IList{T}"/> of the <see cref="ProductFeedbackE"/>
		/// whose attributes are to be retrieved.</param>
		/// <returns>A <see cref="Dictionary{TKey,TValue}"/> of <see cref="LocalizedProductReviewAttribute"/>
		/// keyed by attribute identifier <see cref="Guid"/>.</returns>
		public Dictionary<Guid, LocalizedProductReviewAttribute> GetProductReviewAttributes(Guid tenantId, string catalogId, string productId, int cultureId, DateTime effectiveDate, IList<ProductFeedbackE> feedback)
		{
			Dictionary<Guid, ProductReviewAttributeAssociationBase> attributeAssociations;
			Dictionary<Guid, LocalizedProductReviewAttribute> attributes;
			ProductReviewAttributeAssociationBase attributeAssociation;
			LocalizedProductReviewAttribute attribute;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);
            Safety.AssertArgumentNotNull("feedback", feedback);

			try
			{
				attributeAssociations = GetProductReviewAttributeAssociations(tenantId, catalogId, productId, effectiveDate);
				attributes = new Dictionary<Guid, LocalizedProductReviewAttribute>();
				foreach (ProductFeedbackE feedbackE in feedback)
				{
                    if (feedbackE.Review != null)
                    {
                        foreach (ProductReviewAttributeValue attributeValue in feedbackE.Review.AttributeValues.Values)
                        {
                            if (attributeAssociations.TryGetValue(attributeValue.AttributeId, out attributeAssociation))
                            {
                                if (!attributes.ContainsKey(attributeValue.AttributeId))
                                {
                                    attribute = GetProductReviewAttribute(tenantId, attributeValue.AttributeId, cultureId, attributeAssociation);
                                    if (attribute != null)
                                    {
                                        attributes.Add(attributeValue.AttributeId, attribute);
                                    }
                                }
                            }
                        }
                    }
                }
				return attributes;
			}
			catch (Exception e)
			{
				ExceptionHandlingService.HandleException(tenantId, e, ProductFeedbackExceptionPolicy.Business);
				throw;
			}
		}

		#endregion // IProductFeedbackMetadataService Members

		#region Implementation

		private ProductFeedbackMetadata GetProductFeedbackMetadataWithCaching(Guid tenantId)
		{
			ProductFeedbackMetadata metadata;

			metadata = ProductFeedbackMetadataCache.GetProductFeedbackMetadata(tenantId);
			if (metadata == null)
			{
				metadata = ProductFeedbackMetadataProvider.GetProductFeedbackMetadata(tenantId);
				ProductFeedbackMetadataCache.SetProductFeedbackMetadata(tenantId, metadata);
			}
			return metadata;
		}

        private ProductFeedbackBadgeCategoryRelations GetProductFeedbackBadgeCategoryRelationsWithCaching(Guid tenantId, string catalogId, string categoryId)
        {
            ProductFeedbackBadgeCategoryRelations relations;

            Safety.AssertArgumentNotNull("categoryId", categoryId);

            relations = ProductFeedbackMetadataCache.GetProductFeedbackBadgeCategoryRelations(tenantId, catalogId, categoryId);
            if (relations == null)
            {
                relations = ProductFeedbackMetadataProvider.GetProductFeedbackBadgeCategoryRelations(tenantId, catalogId, categoryId);
                ProductFeedbackMetadataCache.SetProductFeedbackBadgeCategoryRelations(tenantId, catalogId, categoryId, relations);
            }
            return relations;
        }

        private ProductFeedbackBadgeProductRelations GetProductFeedbackBadgeProductRelationsWithCaching(Guid tenantId, string catalogId, string productId)
        {
            ProductFeedbackBadgeProductRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

            relations = ProductFeedbackMetadataCache.GetProductFeedbackBadgeProductRelations(tenantId, catalogId, productId);
            if (relations == null)
            {
                relations = ProductFeedbackMetadataProvider.GetProductFeedbackBadgeProductRelations(tenantId, catalogId, productId);
                ProductFeedbackMetadataCache.SetProductFeedbackBadgeProductRelations(tenantId, catalogId, productId, relations);
            }
            return relations;
        }

        private ProductRatingAttributeCategoryRelations GetProductRatingAttributeCategoryRelationsWithCaching(Guid tenantId, string catalogId, string categoryId)
		{
			ProductRatingAttributeCategoryRelations relations;

			Safety.AssertArgumentNotNull("categoryId", categoryId);

			relations = ProductFeedbackMetadataCache.GetProductRatingAttributeCategoryRelations(tenantId, catalogId, categoryId);
			if (relations == null)
			{
				relations = ProductFeedbackMetadataProvider.GetProductRatingAttributeCategoryRelations(tenantId, catalogId, categoryId);
				ProductFeedbackMetadataCache.SetProductRatingAttributeCategoryRelations(tenantId, catalogId, categoryId, relations);
			}
			return relations;
		}

		private ProductRatingAttributeProductRelations GetProductRatingAttributeProductRelationsWithCaching(Guid tenantId, string catalogId, string productId)
		{
			ProductRatingAttributeProductRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			relations = ProductFeedbackMetadataCache.GetProductRatingAttributeProductRelations(tenantId, catalogId, productId);
			if (relations == null)
			{
				relations = ProductFeedbackMetadataProvider.GetProductRatingAttributeProductRelations(tenantId, catalogId, productId);
				ProductFeedbackMetadataCache.SetProductRatingAttributeProductRelations(tenantId, catalogId, productId, relations);
			}
			return relations;
		}

		private ProductRecommendationAttributeCategoryRelations GetProductRecommendationAttributeCategoryRelationsWithCaching(Guid tenantId, string catalogId, string categoryId)
		{
			ProductRecommendationAttributeCategoryRelations relations;

			Safety.AssertArgumentNotNull("categoryId", categoryId);

			relations = ProductFeedbackMetadataCache.GetProductRecommendationAttributeCategoryRelations(tenantId, catalogId, categoryId);
			if (relations == null)
			{
				relations = ProductFeedbackMetadataProvider.GetProductRecommendationAttributeCategoryRelations(tenantId, catalogId, categoryId);
				ProductFeedbackMetadataCache.SetProductRecommendationAttributeCategoryRelations(tenantId, catalogId, categoryId, relations);
			}
			return relations;
		}

		private ProductRecommendationAttributeProductRelations GetProductRecommendationAttributeProductRelationsWithCaching(Guid tenantId, string catalogId, string productId)
		{
			ProductRecommendationAttributeProductRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			relations = ProductFeedbackMetadataCache.GetProductRecommendationAttributeProductRelations(tenantId, catalogId, productId);
			if (relations == null)
			{
				relations = ProductFeedbackMetadataProvider.GetProductRecommendationAttributeProductRelations(tenantId, catalogId, productId);
				ProductFeedbackMetadataCache.SetProductRecommendationAttributeProductRelations(tenantId, catalogId, productId, relations);
			}
			return relations;
		}

		private ProductReviewAttributeCategoryRelations GetProductReviewAttributeCategoryRelationsWithCaching(Guid tenantId, string catalogId, string categoryId)
		{
			ProductReviewAttributeCategoryRelations relations;

			Safety.AssertArgumentNotNull("categoryId", categoryId);

			relations = ProductFeedbackMetadataCache.GetProductReviewAttributeCategoryRelations(tenantId, catalogId, categoryId);
			if (relations == null)
			{
				relations = ProductFeedbackMetadataProvider.GetProductReviewAttributeCategoryRelations(tenantId, catalogId, categoryId);
				ProductFeedbackMetadataCache.SetProductReviewAttributeCategoryRelations(tenantId, catalogId, categoryId, relations);
			}
			return relations;
		}

		private ProductReviewAttributeProductRelations GetProductReviewAttributeProductRelationsWithCaching(Guid tenantId, string catalogId, string productId)
		{
			ProductReviewAttributeProductRelations relations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			relations = ProductFeedbackMetadataCache.GetProductReviewAttributeProductRelations(tenantId, catalogId, productId);
			if (relations == null)
			{
				relations = ProductFeedbackMetadataProvider.GetProductReviewAttributeProductRelations(tenantId, catalogId, productId);
				ProductFeedbackMetadataCache.SetProductReviewAttributeProductRelations(tenantId, catalogId, productId, relations);
			}
			return relations;
		}

        private IList<CategoryPath> GetAllCategoryPathsToProduct(Guid tenantId, string catalogId, string productId)
		{
			GetAllCategoryPathsToProductRequest request;
			GetAllCategoryPathsToProductResponse response;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			// NOTE: The results should be cached in the target service.  Do *not* cache the
			// results here, as it won't add significant value.
            request = new GetAllCategoryPathsToProductRequest
            {
                TenantId = tenantId,
                ProductId = new FullyQualifiedProductId
                {
                    CatalogId = catalogId,
                    ProductId = productId,
                },
            };
			response = ProductCatalogService.GetAllCategoryPathsToProduct(request);
			return response.Paths;
		}

        private ProductFeedbackBadge GetProductFeedbackBadge(Guid tenantId, Guid badgeId)
        {
            ProductFeedbackMetadata metadata;
            ProductFeedbackBadge badge;

            try
            {
                metadata = GetProductFeedbackMetadataWithCaching(tenantId);
                metadata.Badges.TryGetValue(badgeId, out badge);
                return badge;
            }
            catch (Exception e)
            {
                ExceptionHandlingService.HandleException(tenantId, e, ProductFeedbackExceptionPolicy.Business);
                throw;
            }
        }

        private Dictionary<Guid, AssociationBase> GetProductFeedbackBadgeAssociations(Guid tenantId, string catalogId, string productId, DateTime effectiveDate)
        {
            Dictionary<Guid, AssociationBase> associations;
            IList<CategoryPath> categoryPaths;
            Dictionary<string, ProductFeedbackBadgeCategoryRelations> categoryRelationsCollection;
            Dictionary<Guid, ProductFeedbackBadgeCategoryAssociation> categoryAssociations;
            ProductFeedbackBadgeProductRelations productRelations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

            associations = new Dictionary<Guid, AssociationBase>();

            // merge category associations for each category along each category path.
            categoryPaths = GetAllCategoryPathsToProduct(tenantId, catalogId, productId);
            categoryRelationsCollection = GetProductFeedbackBadgeCategoryRelationsForCategoryPaths(tenantId, catalogId, categoryPaths);
            foreach (CategoryPath categoryPath in categoryPaths)
            {
                categoryAssociations = RelationsServiceTools.GetAssociationsForCategories<ProductFeedbackBadgeCategoryAssociation, ProductFeedbackBadgeCategoryDisassociation>(
                    delegate(string categoryId)
                    {
                        return categoryRelationsCollection[categoryId].Associations;
                    },
                    delegate(string categoryId)
                    {
                        return categoryRelationsCollection[categoryId].Disassociations;
                    },
                    categoryPath.Categories,
                    effectiveDate
                );
                RelationsServiceTools.MergeAssociations<ProductFeedbackBadgeCategoryAssociation, AssociationBase>(categoryAssociations, associations, effectiveDate);
            }

            // merge product relations.
            productRelations = GetProductFeedbackBadgeProductRelationsWithCaching(tenantId, catalogId, productId);
            RelationsServiceTools.MergeAssociationsAndDisassociations<ProductFeedbackBadgeProductAssociation, ProductFeedbackBadgeProductDisassociation, AssociationBase>(productRelations.Associations, productRelations.Disassociations, associations, effectiveDate);

            return associations;
        }

        private LocalizedProductRatingAttribute GetProductRatingAttribute(Guid tenantId, Guid attributeId, int cultureId, ProductRatingAttributeAssociationBase association)
		{
			ProductFeedbackMetadata metadata;
			ProductRatingAttribute attribute;
			ProductRatingAttributeLocalization localization;

			Safety.AssertArgumentNotNull("association", association);

			try
			{
				metadata = GetProductFeedbackMetadataWithCaching(tenantId);
				if (!metadata.RatingAttributes.TryGetValue(attributeId, out attribute))
				{
					return null;
				}
				attribute.Localizations.TryGetValue(cultureId, out localization);
				return new LocalizedProductRatingAttribute(attribute, localization, association);
			}
			catch (Exception e)
			{
				ExceptionHandlingService.HandleException(tenantId, e, ProductFeedbackExceptionPolicy.Business);
				throw;
			}
		}

		private Dictionary<Guid, ProductRatingAttributeAssociationBase> GetProductRatingAttributeAssociations(Guid tenantId, string catalogId, string productId, DateTime effectiveDate)
		{
			Dictionary<Guid, ProductRatingAttributeAssociationBase> associations;
			IList<CategoryPath> categoryPaths;
			Dictionary<string, ProductRatingAttributeCategoryRelations> categoryRelationsCollection;
			Dictionary<Guid, ProductRatingAttributeCategoryAssociation> categoryAssociations;
			ProductRatingAttributeProductRelations productRelations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			associations = new Dictionary<Guid, ProductRatingAttributeAssociationBase>();

			// merge category associations for each category along each category path.
			categoryPaths = GetAllCategoryPathsToProduct(tenantId, catalogId, productId);
			categoryRelationsCollection = GetProductRatingAttributeCategoryRelationsForCategoryPaths(tenantId, catalogId, categoryPaths);
			foreach (CategoryPath categoryPath in categoryPaths)
			{
				categoryAssociations = RelationsServiceTools.GetAssociationsForCategories<ProductRatingAttributeCategoryAssociation, ProductRatingAttributeCategoryDisassociation>(
					delegate(string categoryId)
					{
						return categoryRelationsCollection[categoryId].Associations;
					},
					delegate(string categoryId)
					{
						return categoryRelationsCollection[categoryId].Disassociations;
					},
					categoryPath.Categories,
					effectiveDate
				);
				RelationsServiceTools.MergeAssociations<ProductRatingAttributeCategoryAssociation, ProductRatingAttributeAssociationBase>(categoryAssociations, associations, effectiveDate);
			}

			// merge product relations.
			productRelations = GetProductRatingAttributeProductRelationsWithCaching(tenantId, catalogId, productId);
			RelationsServiceTools.MergeAssociationsAndDisassociations<ProductRatingAttributeProductAssociation, ProductRatingAttributeProductDisassociation, ProductRatingAttributeAssociationBase>(productRelations.Associations, productRelations.Disassociations, associations, effectiveDate);

			return associations;
		}

		private LocalizedProductRecommendationAttribute GetProductRecommendationAttribute(Guid tenantId, Guid attributeId, int cultureId, ProductRecommendationAttributeAssociationBase association)
		{
			ProductFeedbackMetadata metadata;
			ProductRecommendationAttribute attribute;
			ProductRecommendationAttributeLocalization localization;

			Safety.AssertArgumentNotNull("association", association);

			try
			{
				metadata = GetProductFeedbackMetadataWithCaching(tenantId);
				if (!metadata.RecommendationAttributes.TryGetValue(attributeId, out attribute))
				{
					return null;
				}
				attribute.Localizations.TryGetValue(cultureId, out localization);
				return new LocalizedProductRecommendationAttribute(attribute, localization, association);
			}
			catch (Exception e)
			{
				ExceptionHandlingService.HandleException(tenantId, e, ProductFeedbackExceptionPolicy.Business);
				throw;
			}
		}

		private Dictionary<Guid, ProductRecommendationAttributeAssociationBase> GetProductRecommendationAttributeAssociations(Guid tenantId, string catalogId, string productId, DateTime effectiveDate)
		{
			Dictionary<Guid, ProductRecommendationAttributeAssociationBase> associations;
			IList<CategoryPath> categoryPaths;
			Dictionary<string, ProductRecommendationAttributeCategoryRelations> categoryRelationsCollection;
			Dictionary<Guid, ProductRecommendationAttributeCategoryAssociation> categoryAssociations;
			ProductRecommendationAttributeProductRelations productRelations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			associations = new Dictionary<Guid, ProductRecommendationAttributeAssociationBase>();

			// merge category associations for each category along each category path.
			categoryPaths = GetAllCategoryPathsToProduct(tenantId, catalogId, productId);
			categoryRelationsCollection = GetProductRecommendationAttributeCategoryRelationsForCategoryPaths(tenantId, catalogId, categoryPaths);
			foreach (CategoryPath categoryPath in categoryPaths)
			{
				categoryAssociations = RelationsServiceTools.GetAssociationsForCategories<ProductRecommendationAttributeCategoryAssociation, ProductRecommendationAttributeCategoryDisassociation>(
					delegate(string categoryId)
					{
						return categoryRelationsCollection[categoryId].Associations;
					},
					delegate(string categoryId)
					{
						return categoryRelationsCollection[categoryId].Disassociations;
					},
					categoryPath.Categories,
					effectiveDate
				);
				RelationsServiceTools.MergeAssociations<ProductRecommendationAttributeCategoryAssociation, ProductRecommendationAttributeAssociationBase>(categoryAssociations, associations, effectiveDate);
			}

			// merge product relations.
			productRelations = GetProductRecommendationAttributeProductRelationsWithCaching(tenantId, catalogId, productId);
			RelationsServiceTools.MergeAssociationsAndDisassociations<ProductRecommendationAttributeProductAssociation, ProductRecommendationAttributeProductDisassociation, ProductRecommendationAttributeAssociationBase>(productRelations.Associations, productRelations.Disassociations, associations, effectiveDate);

			return associations;
		}

		private LocalizedProductReviewAttribute GetProductReviewAttribute(Guid tenantId, Guid attributeId, int cultureId, ProductReviewAttributeAssociationBase association)
		{
			ProductFeedbackMetadata metadata;
			ProductReviewAttribute attribute;
			ProductReviewAttributeLocalization localization;

			Safety.AssertArgumentNotNull("association", association);

			try
			{
				metadata = GetProductFeedbackMetadataWithCaching(tenantId);
				if (!metadata.ReviewAttributes.TryGetValue(attributeId, out attribute))
				{
					return null;
				}
				attribute.Localizations.TryGetValue(cultureId, out localization);
				return new LocalizedProductReviewAttribute(attribute, localization, association);
			}
			catch (Exception e)
			{
				ExceptionHandlingService.HandleException(tenantId, e, ProductFeedbackExceptionPolicy.Business);
				throw;
			}
		}

		private Dictionary<Guid, ProductReviewAttributeAssociationBase> GetProductReviewAttributeAssociations(Guid tenantId, string catalogId, string productId, DateTime effectiveDate)
		{
			Dictionary<Guid, ProductReviewAttributeAssociationBase> associations;
			IList<CategoryPath> categoryPaths;
			Dictionary<string, ProductReviewAttributeCategoryRelations> categoryRelationsCollection;
			Dictionary<Guid, ProductReviewAttributeCategoryAssociation> categoryAssociations;
			ProductReviewAttributeProductRelations productRelations;

            Safety.AssertArgumentNotNull("catalogId", catalogId);
            Safety.AssertArgumentNotNull("productId", productId);

			associations = new Dictionary<Guid, ProductReviewAttributeAssociationBase>();

			// merge category associations for each category along each category path.
			categoryPaths = GetAllCategoryPathsToProduct(tenantId, catalogId, productId);
			categoryRelationsCollection = GetProductReviewAttributeCategoryRelationsForCategoryPaths(tenantId, catalogId, categoryPaths);
			foreach (CategoryPath categoryPath in categoryPaths)
			{
				categoryAssociations = RelationsServiceTools.GetAssociationsForCategories<ProductReviewAttributeCategoryAssociation, ProductReviewAttributeCategoryDisassociation>(
					delegate(string categoryId)
					{
						return categoryRelationsCollection[categoryId].Associations;
					},
					delegate(string categoryId)
					{
						return categoryRelationsCollection[categoryId].Disassociations;
					},
					categoryPath.Categories,
					effectiveDate
				);
				RelationsServiceTools.MergeAssociations<ProductReviewAttributeCategoryAssociation, ProductReviewAttributeAssociationBase>(categoryAssociations, associations, effectiveDate);
			}

			// merge product relations.
			productRelations = GetProductReviewAttributeProductRelationsWithCaching(tenantId, catalogId, productId);
			RelationsServiceTools.MergeAssociationsAndDisassociations<ProductReviewAttributeProductAssociation, ProductReviewAttributeProductDisassociation, ProductReviewAttributeAssociationBase>(productRelations.Associations, productRelations.Disassociations, associations, effectiveDate);

			return associations;
		}

        private Dictionary<string, ProductFeedbackBadgeCategoryRelations> GetProductFeedbackBadgeCategoryRelationsForCategoryPaths(Guid tenantId, string catalogId, IList<CategoryPath> categoryPaths)
        {
            Dictionary<string, ProductFeedbackBadgeCategoryRelations> categoryRelationsCollection;
            ProductFeedbackBadgeCategoryRelations categoryRelations;

            Safety.AssertArgumentNotNull("categoryPaths", categoryPaths);

            categoryRelationsCollection = new Dictionary<string, ProductFeedbackBadgeCategoryRelations>();
            foreach (CategoryPath categoryPath in categoryPaths)
            {
                foreach (string categoryId in categoryPath.Categories)
                {
                    if (!categoryRelationsCollection.ContainsKey(categoryId))
                    {
                        categoryRelations = GetProductFeedbackBadgeCategoryRelationsWithCaching(tenantId, catalogId, categoryId);
                        categoryRelationsCollection.Add(categoryId, categoryRelations);
                    }
                }
            }
            return categoryRelationsCollection;
        }

        private Dictionary<string, ProductRatingAttributeCategoryRelations> GetProductRatingAttributeCategoryRelationsForCategoryPaths(Guid tenantId, string catalogId, IList<CategoryPath> categoryPaths)
		{
			Dictionary<string, ProductRatingAttributeCategoryRelations> categoryRelationsCollection;
			ProductRatingAttributeCategoryRelations categoryRelations;

			Safety.AssertArgumentNotNull("categoryPaths", categoryPaths);

			categoryRelationsCollection = new Dictionary<string, ProductRatingAttributeCategoryRelations>();
			foreach (CategoryPath categoryPath in categoryPaths)
			{
				foreach (string categoryId in categoryPath.Categories)
				{
					if (!categoryRelationsCollection.ContainsKey(categoryId))
					{
						categoryRelations = GetProductRatingAttributeCategoryRelationsWithCaching(tenantId, catalogId, categoryId);
						categoryRelationsCollection.Add(categoryId, categoryRelations);
					}
				}
			}
			return categoryRelationsCollection;
		}

		private Dictionary<string, ProductRecommendationAttributeCategoryRelations> GetProductRecommendationAttributeCategoryRelationsForCategoryPaths(Guid tenantId, string catalogId, IList<CategoryPath> categoryPaths)
		{
			Dictionary<string, ProductRecommendationAttributeCategoryRelations> categoryRelationsCollection;
			ProductRecommendationAttributeCategoryRelations categoryRelations;

			Safety.AssertArgumentNotNull("categoryPaths", categoryPaths);

			categoryRelationsCollection = new Dictionary<string, ProductRecommendationAttributeCategoryRelations>();
			foreach (CategoryPath categoryPath in categoryPaths)
			{
				foreach (string categoryId in categoryPath.Categories)
				{
					if (!categoryRelationsCollection.ContainsKey(categoryId))
					{
						categoryRelations = GetProductRecommendationAttributeCategoryRelationsWithCaching(tenantId, catalogId, categoryId);
						categoryRelationsCollection.Add(categoryId, categoryRelations);
					}
				}
			}
			return categoryRelationsCollection;
		}

		private Dictionary<string, ProductReviewAttributeCategoryRelations> GetProductReviewAttributeCategoryRelationsForCategoryPaths(Guid tenantId, string catalogId, IList<CategoryPath> categoryPaths)
		{
			Dictionary<string, ProductReviewAttributeCategoryRelations> categoryRelationsCollection;
			ProductReviewAttributeCategoryRelations categoryRelations;

			Safety.AssertArgumentNotNull("categoryPaths", categoryPaths);

			categoryRelationsCollection = new Dictionary<string, ProductReviewAttributeCategoryRelations>();
			foreach (CategoryPath categoryPath in categoryPaths)
			{
				foreach (string categoryId in categoryPath.Categories)
				{
					if (!categoryRelationsCollection.ContainsKey(categoryId))
					{
						categoryRelations = GetProductReviewAttributeCategoryRelationsWithCaching(tenantId, catalogId, categoryId);
						categoryRelationsCollection.Add(categoryId, categoryRelations);
					}
				}
			}
			return categoryRelationsCollection;
		}

		#endregion // Implementation

	}

}
